#!/usr/bin/env python3
"""
Hyperliquid Perpetual Connector for Hummingbot

This connector implements the Hummingbot connector interface for Hyperliquid perpetual trading.
"""

import asyncio
import logging
from decimal import Decimal
from typing import Dict, List, Optional, Any
from urllib.parse import urljoin

import aiohttp
from framework.strategy_base import OrderType, TradeType, PositionAction, PositionSide, Position, OrderBook
from framework.connector_base import ConnectorBase, NetworkStatus, TradingRule, InFlightOrder, TokenAmount, TradeFeeBase

from config.settings import Config
from utils.hyperliquid_api import HyperliquidAPIClient


def safe_ensure_future(coro):
    """Safely create an asyncio task"""
    return asyncio.create_task(coro)


class HyperliquidPerpetualConnector(ConnectorBase):
    """
    Hyperliquid Perpetual Connector implementing Hummingbot's DerivativeBase interface
    """
    
    def __init__(self, 
                 trading_pairs: List[str],
                 trading_required: bool = True,
                 domain: str = "hyperliquid_perpetual"):
        """
        Initialize the Hyperliquid Perpetual Connector
        
        Args:
            trading_pairs: List of trading pairs to trade
            trading_required: Whether trading is required
            domain: Domain identifier
        """
        self._config = Config()
        self._domain = domain
        self._trading_required = trading_required
        
        # API endpoints
        self._base_url = "https://api.hyperliquid.xyz"
        self._info_url = urljoin(self._base_url, "/info")
        self._exchange_url = urljoin(self._base_url, "/exchange")
        
        # Initialize parent class
        super().__init__(trading_pairs)
        
        # Trading pairs and rules
        self._trading_pairs = trading_pairs
        self._trading_rules: Dict[str, TradingRule] = {}
        
        # Order and position tracking
        self._in_flight_orders: Dict[str, InFlightOrder] = {}
        self._account_positions: Dict[str, Position] = {}
        
        # Network status
        self._network_status = NetworkStatus.NOT_CONNECTED
        
        # API client
        self._api_client = HyperliquidAPIClient(
            api_key=self._config.API_WALLET_PRIVATE_KEY,
            api_secret=self._config.API_WALLET_ADDRESS,
            testnet=self._config.TEST_MODE
        )
        self._rest_assistant = None
        
        # Logger
        self._logger = logging.getLogger(__name__)
        
        # Start background tasks
        self._start_background_tasks()
    
    @property
    def name(self) -> str:
        """Exchange name"""
        return "hyperliquid_perpetual"
    
    @property
    def display_name(self) -> str:
        """Display name for the exchange"""
        return "Hyperliquid Perpetual"
    
    @property
    def domain(self) -> str:
        """Domain identifier"""
        return self._domain
    
    @property
    def trading_pairs(self) -> List[str]:
        """List of trading pairs"""
        return self._trading_pairs
    
    @property
    def trading_rules(self) -> Dict[str, TradingRule]:
        """Trading rules for each pair"""
        return self._trading_rules
    
    @property
    def in_flight_orders(self) -> Dict[str, InFlightOrder]:
        """In-flight orders"""
        return self._in_flight_orders
    
    @property
    def account_positions(self) -> Dict[str, Position]:
        """Account positions"""
        return self._account_positions
    
    @property
    def status_dict(self) -> Dict[str, bool]:
        """Status dictionary"""
        return {
            "account_balance": len(self._account_balances) > 0,
            "trading_rule_initialized": len(self._trading_rules) > 0,
            "order_books_initialized": len(self._order_books) > 0,
            "account_positions": True,  # Positions can be empty
        }
    
    @property
    def ready(self) -> bool:
        """Check if connector is ready"""
        return all(self.status_dict.values())
    
    def _start_background_tasks(self):
        """Start background tasks"""
        safe_ensure_future(self._update_trading_rules())
        safe_ensure_future(self._update_balances())
        safe_ensure_future(self._update_positions())
        safe_ensure_future(self._update_order_status())
    
    async def _update_trading_rules(self):
        """Update trading rules from exchange"""
        try:
            # Get trading rules from Hyperliquid API
            async with aiohttp.ClientSession() as session:
                async with session.post(self._info_url, json={"type": "meta"}) as response:
                    if response.status == 200:
                        data = await response.json()
                        universe = data.get("universe", [])
                        
                        for asset_info in universe:
                            symbol = asset_info.get("name")
                            if symbol:
                                # Create trading rule for perpetual
                                trading_rule = TradingRule(
                                    trading_pair=symbol,
                                    min_order_size=Decimal("0.001"),  # Default minimum
                                    max_order_size=Decimal("1000000"),  # Default maximum
                                    has_margin=True,  # Perpetuals have margin
                                    buy_order_collateral_token="USDC",  # Hyperliquid uses USDC
                                    sell_order_collateral_token="USDC",
                                )
                                self._trading_rules[symbol] = trading_rule
                        
                        self._logger.info(f"Updated trading rules for {len(self._trading_rules)} perpetual pairs")
                    else:
                        self._logger.error(f"Failed to fetch trading rules: {response.status}")
        except Exception as e:
            self._logger.error(f"Error updating trading rules: {e}")
    
    async def _update_balances(self):
        """Update account balances"""
        try:
            # Get balances from Hyperliquid API
            user_address = self._config.API_WALLET_ADDRESS
            if not user_address:
                self._logger.warning("No wallet address configured")
                return
            
            async with aiohttp.ClientSession() as session:
                payload = {
                    "type": "clearinghouseState",
                    "user": user_address
                }
                async with session.post(self._info_url, json=payload) as response:
                    if response.status == 200:
                        data = await response.json()
                        
                        # Update margin info
                        margin_summary = data.get("marginSummary", {})
                        account_value = Decimal(margin_summary.get("accountValue", "0"))
                        total_margin_used = Decimal(margin_summary.get("totalMarginUsed", "0"))
                        
                        # Update USDC balance (main collateral)
                        self._account_balances["USDC"] = account_value
                        self._account_available_balances["USDC"] = account_value - total_margin_used
                        
                        self._logger.info(f"Updated perpetual balances - Account Value: {account_value} USDC")
                    else:
                        self._logger.error(f"Failed to fetch balances: {response.status}")
        except Exception as e:
            self._logger.error(f"Error updating balances: {e}")
    
    async def _update_positions(self):
        """Update account positions"""
        try:
            user_address = self._config.API_WALLET_ADDRESS
            if not user_address:
                return
            
            async with aiohttp.ClientSession() as session:
                payload = {
                    "type": "clearinghouseState",
                    "user": user_address
                }
                async with session.post(self._info_url, json=payload) as response:
                    if response.status == 200:
                        data = await response.json()
                        asset_positions = data.get("assetPositions", [])
                        
                        # Clear existing positions
                        self._account_positions.clear()
                        
                        for pos_data in asset_positions:
                            position_info = pos_data.get("position", {})
                            coin = position_info.get("coin")
                            size = Decimal(position_info.get("szi", "0"))
                            
                            if coin and size != 0:
                                # Determine position side
                                position_side = PositionSide.LONG if size > 0 else PositionSide.SHORT
                                
                                # Create position object
                                position = Position(
                                    trading_pair=coin,
                                    position_side=position_side,
                                    unrealized_pnl=Decimal(pos_data.get("unrealizedPnl", "0")),
                                    entry_price=Decimal(position_info.get("entryPx", "0")),
                                    amount=abs(size),
                                    leverage=Decimal(position_info.get("leverage", {}).get("value", "1"))
                                )
                                
                                self._account_positions[coin] = position
                        
                        self._logger.info(f"Updated {len(self._account_positions)} positions")
                    else:
                        self._logger.error(f"Failed to fetch positions: {response.status}")
        except Exception as e:
            self._logger.error(f"Error updating positions: {e}")
    
    async def _update_order_status(self):
        """Update order status"""
        try:
            # Update status of in-flight orders
            for order_id, order in list(self._in_flight_orders.items()):
                # Check order status via API
                # Implementation depends on Hyperliquid's order status API
                pass
        except Exception as e:
            self._logger.error(f"Error updating order status: {e}")
    
    async def start_network(self):
        """Start network connections"""
        try:
            self._rest_assistant = await self._web_assistants_factory.get_rest_assistant()
            self._network_status = NetworkStatus.CONNECTED
            self._logger.info("Perpetual network started successfully")
        except Exception as e:
            self._logger.error(f"Failed to start perpetual network: {e}")
            self._network_status = NetworkStatus.NOT_CONNECTED
    
    async def stop_network(self):
        """Stop network connections"""
        self._network_status = NetworkStatus.NOT_CONNECTED
        self._logger.info("Perpetual network stopped")
    
    async def check_network(self) -> NetworkStatus:
        """Check network status"""
        try:
            # Ping the API to check connectivity
            async with aiohttp.ClientSession() as session:
                async with session.post(self._info_url, json={"type": "meta"}) as response:
                    if response.status == 200:
                        self._network_status = NetworkStatus.CONNECTED
                    else:
                        self._network_status = NetworkStatus.NOT_CONNECTED
        except Exception:
            self._network_status = NetworkStatus.NOT_CONNECTED
        
        return self._network_status
    
    async def check_network_status(self) -> NetworkStatus:
        """Check network status (abstract method implementation)"""
        return await self.check_network()
    
    async def update_trading_rules(self):
        """Update trading rules (abstract method implementation)"""
        await self._update_trading_rules()
    
    async def update_balances(self):
        """Update balances (abstract method implementation)"""
        await self._update_balances()
    
    async def get_order_status(self, order_id: str) -> Dict[str, Any]:
        """Get order status (abstract method implementation)"""
        # Implementation for getting order status
        try:
            # This would typically call the API to get order status
            return {
                "order_id": order_id,
                "status": "unknown",
                "filled_amount": Decimal("0"),
                "remaining_amount": Decimal("0")
            }
        except Exception as e:
            self._logger.error(f"Error getting order status: {e}")
            return {}
    
    def get_order_book(self, trading_pair: str) -> OrderBook:
        """Get order book for trading pair"""
        return self._order_books.get(trading_pair)
    
    def get_fee(self,
                base_currency: str,
                quote_currency: str,
                order_type: OrderType,
                order_side: TradeType,
                amount: Decimal,
                price: Decimal = Decimal("NaN"),
                is_maker: Optional[bool] = None) -> TradeFeeBase:
        """Get trading fee for perpetuals"""
        # Hyperliquid perpetual fees: 0.02% maker, 0.05% taker
        fee_percent = Decimal("0.0002") if is_maker else Decimal("0.0005")
        return TokenAmount("USDC", amount * price * fee_percent)
    
    async def place_order(self,
                         order_id: str,
                         trading_pair: str,
                         amount: Decimal,
                         trade_type: TradeType,
                         order_type: OrderType,
                         price: Decimal,
                         position_action: PositionAction = PositionAction.OPEN,
                         **kwargs) -> str:
        """Place a perpetual order"""
        try:
            # Create order payload for Hyperliquid perpetual API
            is_buy = trade_type == TradeType.BUY
            
            order_payload = {
                "type": "order",
                "orders": [{
                    "a": int(amount * 1000000),  # Amount in micro units
                    "b": is_buy,
                    "p": str(price),
                    "s": trading_pair,
                    "r": position_action == PositionAction.CLOSE,  # Reduce only
                    "t": {"limit": {"tif": "Gtc"}} if order_type == OrderType.LIMIT else {"market": {}}
                }],
                "grouping": "na"
            }
            
            # Sign and send order
            # Implementation depends on Hyperliquid's authentication
            
            # Create in-flight order
            in_flight_order = InFlightOrder(
                client_order_id=order_id,
                exchange_order_id=None,  # Will be updated when response received
                trading_pair=trading_pair,
                order_type=order_type,
                trade_type=trade_type,
                amount=amount,
                price=price,
                creation_timestamp=self.current_timestamp
            )
            
            self._in_flight_orders[order_id] = in_flight_order
            
            self._logger.info(f"Placed perpetual order {order_id} for {amount} {trading_pair} at {price}")
            return order_id
            
        except Exception as e:
            self._logger.error(f"Failed to place perpetual order {order_id}: {e}")
            raise
    
    async def cancel_order(self, order_id: str) -> str:
        """Cancel a perpetual order"""
        try:
            if order_id in self._in_flight_orders:
                order = self._in_flight_orders[order_id]
                
                # Cancel order via API
                cancel_payload = {
                    "type": "cancel",
                    "cancels": [{
                        "a": int(order.amount * 1000000),
                        "s": order.trading_pair,
                        "o": order.exchange_order_id
                    }]
                }
                
                # Send cancel request
                # Implementation depends on Hyperliquid's API
                
                self._logger.info(f"Cancelled perpetual order {order_id}")
                return order_id
            else:
                self._logger.warning(f"Perpetual order {order_id} not found for cancellation")
                return order_id
                
        except Exception as e:
            self._logger.error(f"Failed to cancel perpetual order {order_id}: {e}")
            raise
    
    def get_position(self, trading_pair: str) -> Optional[Position]:
        """Get position for trading pair"""
        return self._account_positions.get(trading_pair)
    
    def set_leverage(self, trading_pair: str, leverage: int) -> bool:
        """Set leverage for trading pair"""
        try:
            # Implementation for setting leverage via Hyperliquid API
            self._logger.info(f"Set leverage for {trading_pair} to {leverage}x")
            return True
        except Exception as e:
            self._logger.error(f"Failed to set leverage for {trading_pair}: {e}")
            return False
    
    def tick(self, timestamp: float):
        """Process periodic updates"""
        # Update order books, balances, positions, etc.
        pass
    
    def get_order_price_quantum(self, trading_pair: str, price: Decimal) -> Decimal:
        """Get price quantum for order"""
        return Decimal("0.01")  # Default price step
    
    def get_order_size_quantum(self, trading_pair: str, order_size: Decimal) -> Decimal:
        """Get size quantum for order"""
        return Decimal("0.001")  # Default size step
    
    def quantize_order_amount(self, trading_pair: str, amount: Decimal) -> Decimal:
        """Quantize order amount"""
        quantum = self.get_order_size_quantum(trading_pair, amount)
        return (amount // quantum) * quantum